﻿using System.Collections.Generic;
using System.IO;
using LingDong.IndexEntities.Dictionary;
using LingDong.IndexEntities.Helper;
using LingDong.IndexMerger.Helper;
using LingDong.SerializeProvider;

namespace LingDong.IndexMerger
{
    internal class DictionaryIndex
    {
        #region Constructor

        internal DictionaryIndex()
        {
            subIndexDictionaryList = new List<Dictionary<string, DictionaryIndexEntity>>();
            mergedIndexDictionary = new Dictionary<string, DictionaryIndexEntity>();
            invertedListStreamList = new List<FileStream>();
        }

        #endregion
        

        #region Internal Methods
        
        /// <summary>
        /// Merge all dictionary related subindex to merged index
        /// Include X.Dictionary, X.InvertedList
        /// "X" increases from 0 to max index in SubIndex directory
        /// </summary>
        internal void Merge(DocumentIndex documentIndex)
        {
            // subindex file names
            List<string> dictionaryFnames;
            List<string> invertedListFnames;

            int indexCount = SubIndexNameHelper.GetDictionaryFileNameList(out dictionaryFnames, out invertedListFnames);

            // load all sub index dictionary to main memory
            ReadAllDictionary(dictionaryFnames);
            OpenInvertedListStreams(invertedListFnames);

            // merge inverted lists
            using (FileStream mergedInvertedListStream = new FileStream(IndexNameHelper.GetInvertedListFileName(), FileMode.Create))
            {
                // iterator all terms in merged index dictionary
                foreach (KeyValuePair<string, DictionaryIndexEntity> termPair in mergedIndexDictionary)
                {
                    OperateOneTerm(termPair.Key, termPair.Value, mergedInvertedListStream, indexCount, documentIndex);
                }
            }

            // save merged dictionary
            using (FileStream fs = new FileStream(IndexNameHelper.GetDictionaryFileName(), FileMode.Create))
            {
                Serializer.Serialize<Dictionary<string, DictionaryIndexEntity>>(fs, mergedIndexDictionary);
            }

            // close streams
            CloseInvertedListStreams();
        }

        #endregion
        

        #region Private Methods

        /// <summary>
        /// Get term's inverted list from file
        /// </summary>
        /// <param name="term">the term</param>
        /// <param name="prefixIndex">the "X" in X.InvertedList</param>
        /// <returns></returns>
        private Dictionary<int, InvertedListEntity> GetInvertedListByTerm(string term, int prefixIndex)
        {
            FileStream fs = invertedListStreamList[prefixIndex];
            fs.Position = subIndexDictionaryList[prefixIndex][term].InvertedListOffset;
            return Serializer.Deserialize<Dictionary<int, InvertedListEntity>>(fs);
        }

        /// <summary>
        /// Load all sub index dictionary to main memory
        /// </summary>
        /// <param name="dictionaryFnames"></param>
        private void ReadAllDictionary(List<string> dictionaryFnames)
        {
            int curTermID = 0;
            foreach (string dicFname in dictionaryFnames)
            {
                // read X.Dictionary
                Dictionary<string, DictionaryIndexEntity> termList;
                using (FileStream fs = new FileStream(dicFname, FileMode.Open))
                {
                    termList = Serializer.Deserialize<Dictionary<string, DictionaryIndexEntity>>(fs);
                }

                // add sub index terms to merged index dictionary
                subIndexDictionaryList.Add(termList);
                foreach (KeyValuePair<string, DictionaryIndexEntity> termPair in termList)
                {
                    string term = termPair.Key;
                    DictionaryIndexEntity entity = termPair.Value;

                    if (mergedIndexDictionary.ContainsKey(term))
                    {
                        DictionaryIndexEntity oldEntity = mergedIndexDictionary[term];
                        oldEntity.DF += entity.DF;
                        oldEntity.TotalTF += entity.TotalTF;
                        oldEntity.TotalTFInTitle += entity.TotalTFInTitle;
                        mergedIndexDictionary[term] = oldEntity;
                    }
                    else
                    {
                        entity.ID = curTermID++;
                        mergedIndexDictionary.Add(term, entity);
                    }
                }
            }
        }

        /// <summary>
        /// Copy a term's inverted list distributed in sub indexes to a merged inverted list
        /// </summary>
        /// <param name="term"></param>
        /// <param name="entity"></param>
        /// <param name="mergedInvertedListStream">target stream to write merged invered list to</param>
        /// <param name="indexCount">count of sub index</param>
        /// <param name="documentIndex"></param>
        private void OperateOneTerm(string term, DictionaryIndexEntity entity, FileStream mergedInvertedListStream, int indexCount, DocumentIndex documentIndex)
        {
            Dictionary<int, InvertedListEntity> invertedList = new Dictionary<int, InvertedListEntity>();
            // iterator all sub index dictionary, find out if it has this term
            for (int i = 0; i < indexCount; i++)
            {
                if (!subIndexDictionaryList[i].ContainsKey(term))
                    continue;

                // the term appears in this sub index, add subindex inverted list to merged inverted list
                Dictionary<int, InvertedListEntity> subIndexInvertedList = GetInvertedListByTerm(term, i);
                foreach (InvertedListEntity subEntity in subIndexInvertedList.Values)
                {
                    int newDocID = documentIndex.GetNewDocIDFromOldDocID(i, subEntity.DocID);
                    subEntity.DocID = newDocID;
                    invertedList.Add(newDocID, subEntity);
                }
            }

            // save the merged inverted list to file
            entity.InvertedListOffset = mergedInvertedListStream.Position;
            Serializer.Serialize<Dictionary<int, InvertedListEntity>>(mergedInvertedListStream, invertedList);
        }

        /// <summary>
        /// Open X.InvertedList files
        /// </summary>
        /// <param name="invertedListFnames"></param>
        private void OpenInvertedListStreams(List<string> invertedListFnames)
        {
            foreach (string fileName in invertedListFnames)
            {
                FileStream fs = new FileStream(fileName, FileMode.Open);
                invertedListStreamList.Add(fs);
            }
        }

        /// <summary>
        /// Close X.InvertedList files opend in OpenInvertedListStreams
        /// </summary>
        private void CloseInvertedListStreams()
        {
            foreach (FileStream fs in invertedListStreamList)
            {
                fs.Close();
            }
        }

        #endregion
                

        #region Private Members

        private List<FileStream> invertedListStreamList;
        private List<Dictionary<string, DictionaryIndexEntity>> subIndexDictionaryList;
        private Dictionary<string, DictionaryIndexEntity> mergedIndexDictionary;

        #endregion
    }
}
